package com.xiaoxu.rental.security;

import cn.hutool.core.util.StrUtil;
import com.xiaoxu.rental.util.JwtUtils;
import com.xiaoxu.rental.util.RedisUtils;
import jakarta.annotation.Resource;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

/**
 * @Title: VerifyTokenFilter
 * @Author Mr.Xu
 * @Package com.xiaoxu.rental.security
 * @Date 2024/7/26 11:12
 * @description: 验证token过滤器
 */

@Component
public class VerifyTokenFilter extends OncePerRequestFilter {
    /**
     * Token验证过滤器，用于在每个请求之前验证请求是否包含有效的Token。
     * 继承自OncePerRequestFilter，确保每个请求最多执行一次过滤逻辑。
     */
    // Redis工具类，用于从Redis中获取Token值
    @Resource
    private RedisUtils redisUtils;
    // 自定义用户详情服务，用于根据用户名加载用户详情
    @Resource
    private CustomerUserDetailsService customerUserDetailsService;
    // 登录失败处理器，用于处理认证失败的情况
    @Resource
    private LoginFailedHandler loginFailedHandler;
    // 登录URL，用于判断当前请求是否是登录请求
    @Value("${request.login-url}")
    private String loginUrl;

    /**
     * 执行过滤逻辑。
     * 如果当前请求不是登录请求，则尝试验证Token的有效性。
     *
     * @param request  HTTP请求
     * @param response HTTP响应
     * @param filterChain 过滤器链，用于继续或停止请求处理
     * @throws ServletException 如果过滤过程中出现Servlet相关异常
     * @throws IOException      如果过滤过程中出现IO异常
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        // 获取请求的URI
        String requestURI = request.getRequestURI();
        // 当请求的URL不是登录页面的URL,进行令牌验证
        if (!StrUtil.equals(requestURI, loginUrl)) {
            try {
                validateToken(request, response);
            } catch (AuthenticationException e) {
                // Token验证失败，调用登录失败处理器处理认证失败情况
                loginFailedHandler.onAuthenticationFailure(request, response, e);
            }
        }
        // 继续处理请求
        filterChain.doFilter(request, response);
    }

    /**
     * 验证请求中的Token是否有效。
     * 首先从请求头中获取Token，如果不存在，则尝试从请求参数中获取。
     * 如果Token不存在或已过期，则抛出AuthenticationException异常。
     * 如果Token有效，则根据Token解析出用户名，并加载用户详情，最后设置认证信息到SecurityContextHolder中。
     *
     * @param request  HTTP请求
     * @param response HTTP响应
     * @throws AuthenticationException 如果Token验证失败
     */
    private void validateToken(HttpServletRequest request,
                               HttpServletResponse response) throws AuthenticationException {
        // 尝试从请求头获取token，如果不存在则从请求参数中获取
        String token = request.getHeader("token");
        if (StrUtil.isEmpty(token)) {
            token = request.getParameter("token");
        }
        // 如果token为空，则抛出异常
        if (StrUtil.isEmpty(token)) {
            throw new CustomerAuthenticationException("token为空");
        }
        // 从Redis中获取tokenKey对应的值，用于和请求中的token进行比较
        String tokenKey = "token:" + token;
        String tokenValue = redisUtils.get(tokenKey);
        // 如果Redis中不存在tokenKey对应的值，则抛出token已过期异常
        if (StrUtil.isEmpty(tokenValue)) {
            throw new CustomerAuthenticationException("token已过期");
        }
        // 如果请求中的token和Redis中的token不一致，则抛出token错误异常
        if (!StrUtil.equals(token, tokenValue)) {
            throw new CustomerAuthenticationException("token错误");
        }
        // 解析token，获取用户名
        String username = JwtUtils.parseToken(token)
                .getClaim("username").toString();
        // 如果无法解析token，则抛出token解析失败异常
        if (StrUtil.isEmpty(username)) {
            throw new CustomerAuthenticationException("token解析失败");
        }
        // 根据用户名加载用户详情
        UserDetails userDetails = customerUserDetailsService
                .loadUserByUsername(username);
        // 如果用户不存在，则抛出异常
        if (userDetails == null) {
            throw new CustomerAuthenticationException("用户不存在");
        }
        // 创建认证信息，并设置到SecurityContextHolder中
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(userDetails,
                        null, userDetails.getAuthorities());
        authenticationToken.setDetails(new WebAuthenticationDetailsSource()
                .buildDetails(request));
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
    }
}

